using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
using Services;

namespace Nebula_Viewer
{
  /// <summary>
  /// This is the main type for your game
  /// </summary>
  public class Game1 : Microsoft.Xna.Framework.Game
  {
    public readonly int SIZE = 1024;

    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;

    public Game1()
    {
      graphics = new GraphicsDeviceManager(this);
      Content.RootDirectory = "Nebula Viewer Content";

      graphics.PreferredBackBufferWidth = 512;
      graphics.PreferredBackBufferHeight = 512;
    }

    CameraProvider camera;
    protected override void Initialize()
    {
      camera = new CameraProvider(this);
      Components.Add(camera);

      base.Initialize();
    }

    List<Texture2D> nebula = new List<Texture2D>();
    protected override void LoadContent()
    {
      spriteBatch = new SpriteBatch(GraphicsDevice);

      var stars = Atlantis();
      var faces = new[]{
          CubeMapFace.PositiveX,
          CubeMapFace.NegativeX,
          CubeMapFace.PositiveY,
          CubeMapFace.NegativeY,
          CubeMapFace.PositiveZ,
          CubeMapFace.NegativeZ,
      };
      var data = new Color[stars[0].Width * stars[0].Height];
      foreach (var face in faces)
      {
        stars[(int)face].GetData(data);
        var t = new Texture2D(GraphicsDevice, stars[0].Width, stars[0].Height);
        t.SetData(data);
        nebula.Add(t);
      }
    }

    protected override void Update(GameTime gameTime)
    {
      if (Keyboard.GetState().IsKeyDown(Keys.Space))
      {
        var faces = new[]{
          CubeMapFace.PositiveX,
          CubeMapFace.NegativeX,
          CubeMapFace.PositiveY,
          CubeMapFace.NegativeY,
          CubeMapFace.PositiveZ,
          CubeMapFace.NegativeZ,
      };
        foreach (var face in faces)
        {
          var t = nebula[(int)face];
          t.SaveAsPng(new System.IO.StreamWriter(@".\" + face.ToString() + ".png").BaseStream, t.Width, t.Height);
        }
        Console.Beep();
      }

      base.Update(gameTime);
    }

    private Texture2D[] YellowShipWithAsteroids()
    {
      var yellow_ship = Content.Load<Model>("yellow_fighter");
      var asteroids = new List<Model>{
          Content.Load<Model>("asteroid1"),
          Content.Load<Model>("asteroid2")
        };
      EnableDefaultLighting(yellow_ship);
      asteroids.ForEach(EnableDefaultLighting);

      var ships = new[]{
          new Generator.ModelInstance(yellow_ship, Matrix.CreateScale(2.4f) * 
            Matrix.CreateRotationX(MathHelper.Pi) *
            Matrix.CreateRotationY(-MathHelper.PiOver2) *
            Matrix.CreateTranslation(new Vector3(-0.5f, 0.75f, 0.0f))),
        };

      var asteroids_X = 10;
      var asteroids_Y = 10;
      var asteroids_Z = 10;
      var r = new Random();
      var stars = Generator.generate_texture_nice(new Vector3(0.15f), ships.Concat(
        from i in Enumerable.Range(0, asteroids_X)
        from j in Enumerable.Range(0, 10)
        from k in Enumerable.Range(0, 10)
        let i_alpha = (float)i / asteroids_X + (float)r.NextDouble() * 0.2f - 0.1f
        let j_alpha = (float)j / asteroids_Y + (float)r.NextDouble() * 0.2f - 0.1f
        let k_alpha = (float)k / asteroids_Z + (float)r.NextDouble() * 0.0f - 0.2f
        let radius = (float)r.NextDouble() * 0.5f + 1.0f
        let x = (float)(Math.Cos(i_alpha * MathHelper.TwoPi) * Math.Sin(j_alpha * MathHelper.Pi)) * radius
        let y = (float)(Math.Cos(j_alpha * MathHelper.Pi)) * radius
        let z = (float)(Math.Sin(i_alpha * MathHelper.TwoPi) * Math.Sin(j_alpha * MathHelper.Pi)) * radius
        let asteroid = asteroids[r.Next(asteroids.Count)]
        let alpha = (float)r.NextDouble()
        let scale = (alpha * alpha * alpha * 0.1f + 0.0f) / asteroid.Meshes[0].BoundingSphere.Radius
        let rx = (float)r.NextDouble() * MathHelper.Pi * 2.0f
        let ry = (float)r.NextDouble() * MathHelper.Pi * 2.0f
        let rz = (float)r.NextDouble() * MathHelper.Pi * 2.0f
        select new Generator.ModelInstance(asteroid,
          Matrix.CreateFromYawPitchRoll(ry, rx, rz) *
          Matrix.CreateScale(scale) * Matrix.CreateTranslation(x, y, z))
        ).ToList(),
        50000, 20, 250, Color.Blue, Color.White, Color.Red, Color.DeepPink, GraphicsDevice, Content, spriteBatch, SIZE);
      return stars;
    }

    BoundingSphere GetBoundingSphere(Model model)
    {
      return (from m in model.Meshes select m.BoundingSphere).Aggregate(BoundingSphere.CreateMerged);
    }

    private Texture2D[] Atlantis()
    {
      var atlantis = Content.Load<Model>("atlantis_new");
      EnableDefaultLighting(atlantis);

      var bs = GetBoundingSphere(atlantis);

      var stars = Generator.generate_texture_nice(new Vector3(0.15f),
          new[]{
                    new Generator.ModelInstance(atlantis, 
                        Matrix.CreateTranslation(-bs.Center) *
                        Matrix.CreateScale(1.0f / bs.Radius) *
                        Matrix.CreateScale(0.5f) * 
                        Matrix.CreateRotationZ(1.0f) * 
                    Matrix.CreateRotationX(MathHelper.Pi) *
                    Matrix.CreateRotationY(-MathHelper.PiOver2) *
                    Matrix.CreateTranslation(new Vector3(-0.15f, 0.75f, 0.0f)))
                    },
        50000, 20, 250, Color.LightSeaGreen, Color.LightBlue, Color.Violet, Color.Blue, GraphicsDevice, Content, spriteBatch, SIZE);
      return stars;
    }

    private Texture2D[] HopeGlory()
    {
      var atlantis = Content.Load<Model>("Hope___glory");
      EnableDefaultLighting(atlantis);

      var bs = GetBoundingSphere(atlantis);

      var stars = Generator.generate_texture_nice(new Vector3(0.15f),
          new[]{
                    new Generator.ModelInstance(atlantis, 
                        Matrix.CreateTranslation(-bs.Center) *
                        Matrix.CreateScale(1.0f / bs.Radius) *
                        Matrix.CreateScale(0.5f) * 
                        Matrix.CreateRotationZ(MathHelper.Pi) * 
                    Matrix.CreateRotationX(MathHelper.Pi) *
                    Matrix.CreateRotationY(-MathHelper.PiOver2) *
                    Matrix.CreateTranslation(new Vector3(-0.15f, 0.75f, 0.0f)))
                    },
        500, 20, 250, Color.Blue, Color.White, Color.Red, Color.DeepPink, GraphicsDevice, Content, spriteBatch, SIZE);
      return stars;
    }


    private static void EnableDefaultLighting(Model yellow_ship)
    {
      foreach (var mesh in yellow_ship.Meshes)
      {
        foreach (BasicEffect fx in mesh.Effects)
        {
          fx.EnableDefaultLighting();
        }
      }
    }

    protected override void Draw(GameTime gameTime)
    {
      GraphicsDevice.Clear(Color.CornflowerBlue);

      spriteBatch.Begin();

      var i = (int)CubeMapFace.PositiveY;
      var face = nebula[i];
      spriteBatch.Draw(face, new Rectangle(0 * 512, 0, 512, 512), Color.White);

      spriteBatch.End();

      base.Draw(gameTime);
    }
  }
}
